前言
深知自己在算法方面上很菜,于是打算通过打卡的方式,逼自己一把。每天在leetcode上打卡,并且把自己的想法分享出来。这将是一段漫长而又艰辛的旅程。如果你也想和我一起走上一条充满艰辛的航路,那么,别犹豫了,上车吧,一起学习一起探讨。
从零打卡leetcode之day 3
1 | 题目描述: |
解题
1.初级版解法
对于这道题,其实我们可以采取遍历所有可能的组合,然后再比较哪种组合的和最大。
也就是说,我们可以找出所有子序列,然后逐个比较。代码如下。
1 | public int solve(int[] arrs){ |
在这三个循环中,外面两个循环枚举出所有子序列,第三个循环计算子序列的和。
看到三个for循环,时间复杂度的O(n3)。这速度,实在是太慢了。我们来优化优化。
2.进阶版
其实,你仔细看一下里面的那两层for循环,会发现其实可以把它们合并成一个for循环的。
也就是说,我们可以在枚举所有子序列的过程中,是可以一边进行数据处理的。还是直接看代码好理解点。如下:
1 | public int solve2(int[] arrs){ |
该方法用了两个for循环,时间复杂度为O(n2),相对来说好了一点。
3.再次优化进阶
这次,我们可以使用递归的思想来处理。递归最重要的就是要找到:
- 递归的结束条件
- 把问题分解成若干个子问题。
对于这道题,其实我们可以把序列分成左右两部分。那么,最大子序列和的位置会出现在以下三种情况:
- 子序列完全在左半部分。
- 子序列完全在右半部分。
- 一部分在左,一部分在右。
所以我们只要分别求出左半部分的最大子序列和、右半部分的最大子序列和(注意,问题已经转化为求左右两部分的最大子序列和了,也就是说问题被分解成若干子问题了)、以及跨越左右两部分的最大子序列和。最后比较三者之中哪个比较大就可以了。
如何求解左半部分和右半部分的最大子序列?
其实道理一样,把左半部分和右半部分再次分解左右两部分就可以了。
那么,如何求解跨越左右两部分的最大子序列呢?
其实只要求出包含左半部分中最右边元素的子序列的最大和,以及求出包含右半部分中最左边元素的子序列的最大和,然后让两者相加,即可求出跨域左右两部分的最大子序列和了。
子问题已经分解出来了,那么递归的结束条件是什么?
我们把数组分成左右两部分,其实当左右两部分只有一个元素时,递归结束。
这道题的递归思路算是找出来了,不过,代码实现?假如你对递归不大熟悉的话,可能在实现上还是有那么点困难的。对于递归的学习,大家也可以看我写的关于递归与动态规划的几篇文章。
我就直接抛代码了。
1 | //递归版本 |
递归求解方法的时间复杂度为O(nlgn)。这速度,比第一种做法,不知道快了几个级别….
递归解法可以说是很快的了
但是,等等,我还是不满意…
4.最终版:动态规划
接下来的最终版,时间复杂度可以缩减到O(n), 虽然说是采用了动态规划的思想,不过,我觉得你没学过动态规划也可以看懂。
假如我给你1
1 2 -4 5 6
五个元素,你在计算前面三个元素的时候,即
1 + 2 + -4 = -1
发现前面三个元素的和是小于0的,那么,这个
1 2 -4
的子序列我们还要吗?显然,这个子序列的和都小于0了,我们是可以直接淘汰的。因为如果还要这个子序列的话,它和后面的5一相加,结果变成了4,我们还不如让我们的目标子序列直接从5开始呢。
先看代码吧,可能反而会好理解点
1 | //基于动态规划的思想 |
这道题不是leetcode上的题目,不过我觉得这道题很不错,所以拿出来分享给大家。
如果你有什么不大清楚的,欢迎微信群里讨论,当然也可以直接来问我勒。欢迎转发让更多人加入打卡行列勒。
如果这道题能对你有所帮助,不妨点个赞。哈哈